/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Microsoft Public License. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the  Microsoft Public License, please send an email to 
 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 *
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;

using Microsoft.Scripting.Generation;

namespace Microsoft.Scripting.Actions {
    internal delegate object MatchCallerTarget(object target, CallSite site, object[] args);

    /// <summary>
    /// MatchCaller allows to call match maker delegate with the signature (object, CallSite, object[])
    /// It is used by the call site cache lookup logic when searching for applicable rule.
    /// </summary>
    internal static partial class MatchCaller {
        private static readonly Dictionary<Type, WeakReference> _Callers = new Dictionary<Type, WeakReference>();
        private static readonly Type[] _CallerSignature = new Type[] { typeof(object), typeof(CallSite), typeof(object[]) };

        internal static MatchCallerTarget GetCaller(Type type) {
            bool found;
            WeakReference wr;

            // LOCK to extract the weak reference with the updater DynamicMethod 
            lock (_Callers) {
                found = _Callers.TryGetValue(type, out wr);
            }

            // Extract the DynamicMethod from the WeakReference, if any
            object target = null;
            if (found && wr != null) {
                target = wr.Target;
            }

            // No target? Build new one
            if (target == null) {
                target = CreateCaller(type);

                // Insert into dictionary
                lock (_Callers) {
                    _Callers[type] = new WeakReference(target);
                }
            }

            return (MatchCallerTarget)target;
        }

        /// <summary>
        /// Uses LCG to create method such as this:
        /// 
        /// object MatchCaller(object target, CallSite site, object[] args) {
        ///      return ((ActualDelegateType)target)(site, args[0], args[1], args[2], ...);
        /// }
        /// 
        /// inserting appropriate casts and boxings as needed.
        /// </summary>
        /// <param name="type">Type of the delegate to call</param>
        /// <returns>A MatchCallerTarget delegate.</returns>
        private static object CreateCaller(Type type) {
            MethodInfo invoke = type.GetMethod("Invoke");
            ParameterInfo[] parameters = invoke.GetParameters();

            DynamicILGen il = Snippets.Shared.CreateDynamicMethod("_stub_MatchCaller", typeof(object), _CallerSignature, false);

            // Emit delegate and cast it to the right type
            il.EmitLoadArg(0);
            il.Emit(OpCodes.Castclass, type);
    
            // CallSite
            il.EmitLoadArg(1);

            // Arguments
            for (int i = 1; i < parameters.Length; i++) {
                il.EmitLoadArg(2);
                il.EmitInt(i - 1);
                il.Emit(OpCodes.Ldelem_Ref);
                Type pt = parameters[i].ParameterType;
                if (pt.IsValueType) {
                    il.Emit(OpCodes.Unbox_Any, pt);
                } else if (pt != typeof(object)) {
                    il.Emit(OpCodes.Castclass, pt);
                }
            }

            // Call the delegate
            il.Emit(OpCodes.Callvirt, invoke);

            // Return value
            if (invoke.ReturnType.IsValueType) {
                il.Emit(OpCodes.Box, invoke.ReturnType);
            }

            il.Emit(OpCodes.Ret);

            return il.CreateDelegate<MatchCallerTarget>();
        }
    }
}
